package scenic;

import java.awt.Font;
import java.awt.Rectangle;
import java.awt.font.FontRenderContext;
import java.awt.font.GlyphVector;
import java.awt.geom.AffineTransform;

import scenic.geom.Affine2;
import scenic.path.Path;

/**
 * This shape draws text. The text is specified using glyph codes.
 * The positions of the glyphs are given as an array that
 * contains the x and y coordinates of each glyph.
 */
public class TextShape extends Shape {
	private Font font;
	private int[] glyphCodes;
	private float[] positions;
	private FilledPath[] glyphPolygons;
	
	private static final double polygonSizeLimit = 100.0;
	
	/**
	 * Constructs a TextShape object with the given parameters.
	 * 
	 * @param font the font.
	 * @param glyphCodes an array of glyph codes.
	 * @param positions an array containing the x and y coordinates of each glyph.
	 */
	public TextShape(Font font, int[] glyphCodes, float[] positions) {
		this.font = font;
		this.glyphCodes = glyphCodes;
		this.positions = positions;
	}

	/**
	 * Gets the font.
	 */
	public Font getFont() {
		return font;
	}

	/**
	 * Sets the font.
	 */
	public void setFont(Font font) {
		this.font = font;
		changed();
	}

	/**
	 * Gets the glyph codes.
	 */
	public int[] getGlyphCodes() {
		return glyphCodes;
	}

	/**
	 * Gets the glyph positions.
	 */
	public float[] getPositions() {
		return positions;
	}

	/**
	 * Sets the glyph codes and positions.
	 * 
	 * @param glyphCodes an array of glyph codes.
	 * @param positions an array containing the x and y coordinates of each glyph.
	 */
	public void setGlyphs(int[] glyphCodes, float[] positions) {
		this.glyphCodes = glyphCodes;
		this.positions = positions;
		glyphPolygons = null;
		changed();
	}

	void draw(DrawContext context, Affine2 transform, Color color) {
		if(font == null)
			return;
		
		if(usePolygon(transform))
			drawUsingPolygon(context, transform, color);
		else
			drawUsingTexture(context, transform, color);
	}

	private void drawUsingPolygon(DrawContext context, Affine2 transform, Color color)
	{
		if(glyphPolygons == null) {
			glyphPolygons = new FilledPath[glyphCodes.length];
			for(int i = 0; i < glyphCodes.length; i++) {
				Affine2 affine2 = Affine2.translate(positions[i * 2], positions[i * 2 + 1]);
				AffineTransform at = affine2.getAffineTransform();
				java.awt.Shape shape = TextRenderer.getGlyphOutline(font, 
						at, 
						glyphCodes[i]);
				Path path = new Path(shape, affine2);
				
				glyphPolygons[i] = new FilledPath(path);
				glyphPolygons[i].setAntialias(true);
			}
		}
		
		for(int i = 0; i < glyphPolygons.length; i++) {
			glyphPolygons[i].draw(context, transform, color);
		}
	}
	
	private void drawUsingTexture(DrawContext context, Affine2 transform, Color color)
	{
		AffineTransform at = transform.getLinearTransform();
		
		at.concatenate(font.getTransform());
		GlyphCache.CachedFont drawFont = new GlyphCache.CachedFont(font, at);
		GlyphCache cache = Device.getGlyphCache();
		Image img = (Image)cache.getImage();
		double texm = 1.0 / img.getWidth();
		
		context.renderer.color(context.context, color);
		context.renderer.beginText(context.context);
		context.renderer.setTextTexture(context.context, img.getId());
		
		for(int i = 0; i < glyphCodes.length; i++)
		{
			GlyphImage g = cache.getGlyph(drawFont, glyphCodes[i]);
			
			if(g == null) {
				context.renderer.endText(context.context);
				g = cache.renderGlyph(drawFont, glyphCodes[i]);
				context.renderer.beginText(context.context);				
				context.renderer.setTextTexture(context.context, img.getId());
			}
			if(g != null && g.cache != null && g.width > 0 && g.height > 0)
			{
				Rectangle r = g.cache;
				double px = positions[i * 2];
				double py = positions[i * 2 + 1];
				double tpx = transform.m00 * px + transform.m01 * py + transform.dx;
				double tpy = transform.m10 * px + transform.m11 * py + transform.dy;
				double ix = Math.floor(tpx + 0.5001) - g.x;
				double iy = Math.floor(tpy + 0.5001) - g.y;
				
				context.renderer.drawGlyph(context.context, 
						(float)(r.x * texm), (float)(r.y * texm),
						(float)(r.width * texm), (float)(r.height * texm), 
						(int)ix, (int)iy, g.width, g.height);
			}
		}
		context.renderer.endText(context.context);
	}
	
	private boolean usePolygon(Affine2 transform) {
		return Math.sqrt(transform.determinant()) * font.getSize() > 
			polygonSizeLimit;
	}
	
	int getDrawType(Affine2 transform, Color color) {
		if(usePolygon(transform))
			return DRAW_SURFACE4X;
		else
			return DRAW_SIMPLE;
	}
	
	Rectangle getBounds(Affine2 transform) {
		Rectangle r = null;

		FontRenderContext frc = new FontRenderContext(
				transform.getAffineTransform(),
				false, true);
		for(int i = 0; i < glyphCodes.length; i++) {

			GlyphVector gv = font.createGlyphVector(
					frc,
					new int[] {glyphCodes[i]});
			Rectangle gr = gv.getPixelBounds(frc, positions[i * 2], positions[i * 2 + 1]);
			
			if(r == null)
				r = gr;
			else
				r = r.union(gr);
		}
		if(r == null)
			return new Rectangle();
		return r;
	}
}
